﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint;

namespace ListItemEditor
{
    #region helper types

    public interface ISiteMapProviderRefreshable
    {
        void RefreshNode(SiteMapNode node);
    }

    public enum ListSiteMapNodeType { Root = 0, WebApplication = 1, Web = 2, List = 3, View = 4 }
    public class ListSiteMapNodeData
    {
        private string _url;

        public ListSiteMapNodeType NodeType {get; set; }
        public string Url { get { return _url; } set { _url = NormalizeUrl(value); } }
        public string Title {get; set; }

        public ListSiteMapNodeData(ListSiteMapNodeType nodeType, string url, string title)
        {
            this.NodeType = nodeType;
            this.Url = url;
            this.Title = title;
            
        }

        public static string NormalizeUrl(string url)
        {
            Uri uri;
            if (!Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out uri)) return url;
            return uri.ToString();

        }
    }

    #endregion

    #region ListSiteMapNode

    public class ListSiteMapNode : SiteMapNode
    {
        public object UserData { get; set; }

        public bool ChildrenLoaded { get; private set; }
        public ListSiteMapNodeData NodeData { get; private set; }

        private SiteMapNodeCollection _childNodes;

        public ListSiteMapNode(ListSiteMapProvider provider, ListSiteMapNodeData data, ListSiteMapNode parent)
            : base(provider, data.Url, data.Url, data.Title)
        {
            this.NodeData = data;
            this.ParentNode = parent;
        }

        public override SiteMapNodeCollection ChildNodes
        {
            get
            {
                if (!this.ChildrenLoaded)
                {
                    _childNodes = this.Provider.GetChildNodes(this);
                }
                return _childNodes;
            }
            set
            {
                ChildrenLoaded = true;
                this._childNodes = value;
            }
        }

        public void InvalidateChildren()
        {
            this.ChildrenLoaded = false;
        }
    }

    #endregion

    #region ListSiteMapProvider

    public class ListSiteMapProvider : SiteMapProvider, ISiteMapProviderRefreshable
    {
        private delegate void UseWeb(SPWeb web);

        #region variables

        private ListSiteMapNode _root;
        private Dictionary<string, ListSiteMapNode> _nodeMap;

        #endregion

        #region overrides

        public override SiteMapNode FindSiteMapNode(string rawUrl)
        {
            this.EnsureNodeMap();
            rawUrl = ListSiteMapNodeData.NormalizeUrl(rawUrl);
            if (this._nodeMap.ContainsKey(rawUrl)) return this._nodeMap[rawUrl];
            return null;
        }

        public override SiteMapNodeCollection GetChildNodes(SiteMapNode node)
        {
            ListSiteMapNode listNode = (ListSiteMapNode)node;
            if (listNode.ChildrenLoaded) return listNode.ChildNodes;
            SiteMapNodeCollection childNodes = new SiteMapNodeCollection(this.GetChildren(listNode).ToArray());
            listNode.ChildNodes = childNodes;

            this.AddNodeIfMapExists(listNode);
            return listNode.ChildNodes;
        }

        public override SiteMapNode GetParentNode(SiteMapNode node)
        {
            return node.ParentNode;
        }

        protected override SiteMapNode GetRootNodeCore()
        {
            return this.RootNode;
        }

        public override SiteMapNode RootNode
        {
            get
            {
                if (_root == null)
                {
                    _root = new ListSiteMapNode(this, new ListSiteMapNodeData(ListSiteMapNodeType.Root, string.Empty, string.Empty), null);
                }
                return _root;
            }
        }

        public override SiteMapNode CurrentNode
        {
            get
            {
                return null;
            }
        }

        #endregion

        #region public

        public void RefreshNode(SiteMapNode node)
        {
            ((ListSiteMapNode)node).InvalidateChildren();
            this.InvalidateNodeMap();
        }

        #endregion

        #region private methods

        protected virtual List<ListSiteMapNode> GetChildren(ListSiteMapNode parent)
        {
            List<ListSiteMapNode> children = new List<ListSiteMapNode>();
            switch (parent.NodeData.NodeType)
            {
                case ListSiteMapNodeType.Root:
                    children.AddRange (
                        this.GetWebApplications()
                        .Select(wa => new ListSiteMapNode(this, new ListSiteMapNodeData(ListSiteMapNodeType.WebApplication, wa.AlternateUrls.GetResponseUrl(SPUrlZone.Default).Uri.AbsoluteUri, wa.Name), parent)));
                    break;
                case ListSiteMapNodeType.WebApplication:
                    children.AddRange (
                        this.GetWebApplicationFromUrl(parent.NodeData.Url)
                        .Sites.Cast<SPSite>()
                        .Select(s => { SPWeb rootWeb = s.RootWeb; ListSiteMapNode n = new ListSiteMapNode(this, new ListSiteMapNodeData(ListSiteMapNodeType.Web, rootWeb.Url, rootWeb.ServerRelativeUrl + " (" + rootWeb.Title + ")"), parent); rootWeb.Dispose(); s.Dispose(); return n; }));
                    break;
                case ListSiteMapNodeType.Web:
                    OpenUseWeb(parent.Url,
                        web => 
                        {
                            children.AddRange(
                                web.Webs.Cast<SPWeb>()
                                .Select(w => { ListSiteMapNode n = new ListSiteMapNode(this, new ListSiteMapNodeData(ListSiteMapNodeType.Web, w.Url, w.Name + " (" + w.Title + ")"), parent); w.Dispose(); return n; }).OrderBy(n => n.Title));

                            children.AddRange(
                                web.Lists.Cast<SPList>()
                                .Select(l => new ListSiteMapNode(this, new ListSiteMapNodeData(ListSiteMapNodeType.List, web.Url.TrimEnd('/') + "/" + l.RootFolder.Url, l.Title), parent)).OrderBy(n => n.Title)
                                );
                        });

                    break;
                case ListSiteMapNodeType.List:
                    OpenUseWeb(parent.ParentNode.Url,
                        web =>
                        {
                            children.AddRange(
                            web.Lists[parent.Title].Views.Cast<SPView>()
                                .Where (v => !string.IsNullOrEmpty(v.Title))
                                .Select(v => new ListSiteMapNode(this, new ListSiteMapNodeData(ListSiteMapNodeType.View, web.Url.TrimEnd('/') + "/" + v.Url, v.Title), parent)).OrderBy(n => n.Title)
                                );
                        });
                    break;
                case ListSiteMapNodeType.View:
                    break;
                default:
                    break;
            }
            return children;
        }

        private SPWebApplicationCollection GetWebApplications()
        {
            SPFarm farm = SPFarm.Local;
            SPWebService service = farm.Services.GetValue<SPWebService>();
            return service.WebApplications;
        }

        private SPWebApplication GetWebApplicationFromUrl(string url)
        {
            return this.GetWebApplications().FirstOrDefault(wa => wa.AlternateUrls.GetResponseUrl(SPUrlZone.Default).Uri.AbsoluteUri == url);
        }

        private void OpenUseWeb(string url, UseWeb useWeb)
        {
            using (SPSite site = new SPSite(url))
            {
                using (SPWeb web = site.OpenWeb())
                {
                    useWeb(web);
                }
            }
        }

        private void EnsureNodeMap()
        {
            if (this._nodeMap != null) return;
            this._nodeMap = new Dictionary<string, ListSiteMapNode>(StringComparer.OrdinalIgnoreCase);
            this.AddNodeToMap(this._root);
        }

        private void AddNodeIfMapExists(ListSiteMapNode node)
        {
            if (this._nodeMap == null) return;
            AddNodeToMap(node);
        }

        private void AddNodeToMap(ListSiteMapNode node)
        {
            if (!string.IsNullOrEmpty(node.Key)) this._nodeMap[node.Key] = node;
            if (!node.ChildrenLoaded) return;

            foreach (ListSiteMapNode child in node.ChildNodes.Cast<ListSiteMapNode>())
            {
                this.AddNodeToMap(child);
            }
        }

        private void InvalidateNodeMap()
        {
            this._nodeMap = null;
        }

        #endregion
    }

    #endregion

    #region ListSiteMapProviderWSvc

    public class ListSiteMapProviderWSvc : ListSiteMapProvider
    {
        private GridViewDataSourceSettings _settings;

        public ListSiteMapProviderWSvc(GridViewDataSourceSettings settings)
        {
            this._settings = settings;
        }

        protected override List<ListSiteMapNode> GetChildren(ListSiteMapNode parent)
        {
            List<ListSiteMapNode> children = new List<ListSiteMapNode>();
            GridViewDataSourceSettings settings;
            switch (parent.NodeData.NodeType)
            {
                case ListSiteMapNodeType.Root:
                    string startUrl = _settings.WebUrl.TrimEnd('/');
                    children.Add(
                            new ListSiteMapNode(this, new ListSiteMapNodeData(ListSiteMapNodeType.WebApplication, startUrl, startUrl), parent)
                        );
                    break;
                case ListSiteMapNodeType.WebApplication:
                case ListSiteMapNodeType.Web:
                    settings = this._settings.Clone();
                    if (parent.NodeData.NodeType == ListSiteMapNodeType.Web) settings.WebUrl = parent.NodeData.Url;

                    children.AddRange(WscHelper.Current.GetSubwebs(settings)
                        .Select(wd => new ListSiteMapNode(this, new ListSiteMapNodeData(ListSiteMapNodeType.Web, wd.Url, wd.Title), parent))
                        .OrderBy (n => n.Title));

                    children.AddRange(WscHelper.Current.GetWebLists(settings)
                        .Select(wl => new ListSiteMapNode(this, new ListSiteMapNodeData(ListSiteMapNodeType.List, AddHostToUrl (_settings.WebUrl, wl.Url), wl.Title), parent))
                        .OrderBy (n => n.Title));
                    break;
                case ListSiteMapNodeType.List:
                    settings = this._settings.Clone();
                    settings.WebUrl = parent.ParentNode.Url;
                    settings.ListName = parent.NodeData.Title;

                    children.AddRange(WscHelper.Current.GetListData(settings, false).Views
                        .Where(lv => !string.IsNullOrEmpty(lv.Name))
                        .Select(lv => new ListSiteMapNode(this, new ListSiteMapNodeData(ListSiteMapNodeType.View, AddHostToUrl(_settings.WebUrl, lv.Url), lv.Name), parent))
                        .OrderBy(n => n.Title));
                    break;
                case ListSiteMapNodeType.View:
                    break;
                default:
                    break;
            }
            return children;
        }

        private string AddHostToUrl(string templateUrl, string url)
        {
            return new Uri(templateUrl).GetComponents(UriComponents.SchemeAndServer, UriFormat.Unescaped).TrimEnd('/') + url;
        }
     }

    #endregion
}